package com.gitee.wsl.common.draw.gesture

import android.content.Context
import android.view.GestureDetector
import android.view.MotionEvent
import android.view.ScaleGestureDetector
import com.gitee.wsl.common.draw.gesture.computator.ChartCommutator
import com.gitee.wsl.android.ui.api.ParentInterceptTouchAble
import com.gitee.wsl.common.ui.api.TouchCheckAble

/**
 * Default touch handler for most charts. Handles value touch, scroll, fling and zoom.
 */
open class ChartTouchHandler(context: Context, val commutator: ChartCommutator, private val touchCheckAble: com.gitee.wsl.common.ui.api.TouchCheckAble?, var viewParent: ParentInterceptTouchAble? = null):
    ChartTouch {

    protected val gestureDetector: GestureDetector = GestureDetector(context, ChartGestureListener())

    protected val scaleGestureDetector: ScaleGestureDetector = ScaleGestureDetector(context, ChartScaleGestureListener())

    protected val chartScroller: ChartScroller = ChartScroller(context,commutator)

    //protected val commutator: ChartCommutator by touchCheckAble::chartCommutator

    protected val chartZoomer: ChartZoomer = ChartZoomer(commutator)

    //protected var selectHelper: ChartSelectHelper? by this::chart

    var isZoomEnabled  by commutator::isZoomEnabled
    var isScrollEnabled by commutator::isScrollEnabled
    var isValueTouchEnabled by commutator::isValueTouchEnabled
    var isValueSelectionEnabled by commutator::isValueSelectionEnabled

    var isContainerScrollEnabled by commutator::isContainerScrollEnabled

    /**
     * ViewParent to disallow touch events interception if chart is within scroll container.
     */

    /**
     * Type of scroll of container, horizontal or vertical.
     */
    var containerScrollType: ContainerScrollType = ContainerScrollType.NONE


    /**
     * Computes scroll and zoom using [ChartScroller] and [ChartZoomer]. This method returns true if
     * scroll/zoom was computed and chart needs to be invalidated.
     */
    open fun computeScroll(): Boolean {
        var needInvalidate = false
        if (isScrollEnabled && chartScroller.computeScrollOffset()) {
            needInvalidate = true
        }
        if (isZoomEnabled && chartZoomer.computeZoom()) {
            needInvalidate = true
        }
        return needInvalidate
    }

    /**
     * Handle chart touch event(gestures, clicks). Return true if gesture was handled and chart needs to be
     * invalidated.
     */
    override fun handleTouch(event: MotionEvent): Boolean {
        var needInvalidate = scaleGestureDetector.onTouchEvent(event)
        val isZooming = scaleGestureDetector.isInProgress
        if(isZooming) {
            disallowParentInterceptTouchEvent()
        }else{
            needInvalidate = gestureDetector.onTouchEvent(event)||needInvalidate
        }
        if (!isZooming && isValueTouchEnabled) {
            needInvalidate = computeTouch(event) || needInvalidate
        }

        // TODO: detectors always return true, use class member needInvalidate instead local variable as workaround.
        // This flag should be computed inside gesture listeners methods to avoid invalidation.
        /*needInvalidate = gestureDetector.onTouchEvent(event)
        if (isZoomEnabled) {
            needInvalidate = scaleGestureDetector.onTouchEvent(event) || needInvalidate
            isZooming = scaleGestureDetector.isInProgress
            // Special case: if view is inside scroll container and user is scaling disable touch interception by
            // parent.
            if(isZooming)
               disallowParentInterceptTouchEvent()
        }
        if (!isZooming && isValueTouchEnabled) {
            needInvalidate = computeTouch(event) || needInvalidate
        }*/
        return needInvalidate
    }

    /**
     * Handle chart touch event(gestures, clicks). Return true if gesture was handled and chart needs to be
     * invalidated.
     * If viewParent and containerScrollType are not null chart can be scrolled and scaled within horizontal or
     * vertical
     * scroll container like ViewPager.
     */
    fun handleTouch(
        event: MotionEvent,
        containerScrollType: ContainerScrollType = ContainerScrollType.NONE
    ): Boolean {
        this.containerScrollType = containerScrollType
        return handleTouch(event)
    }

    /**
     * Disallow parent view from intercepting touch events. Use it for chart that is within some scroll container i.e.
     * ViewPager.
     */
    private fun disallowParentInterceptTouchEvent() {
        viewParent?.requestDisallowInterceptTouchEvent(true)
    }

    /**
     * Allow parent view to intercept touch events if chart cannot be scroll horizontally or vertically according to
     * the
     * current value of [.containerScrollType].
     */
    private fun allowParentInterceptTouchEvent(scrollResult: ChartScroller.ScrollResult) {
        viewParent?.let {
            if (ContainerScrollType.HORIZONTAL == containerScrollType && !scrollResult.canScrollX && !scaleGestureDetector.isInProgress) {
                it.requestDisallowInterceptTouchEvent(false)
            } else if (ContainerScrollType.VERTICAL == containerScrollType && !scrollResult.canScrollY && !scaleGestureDetector.isInProgress) {
                it.requestDisallowInterceptTouchEvent(false)
            }
        }
    }

    private fun computeTouch(event: MotionEvent): Boolean {
        if(touchCheckAble == null) return false

        var needInvalidate = false
        when (event.action) {
            MotionEvent.ACTION_DOWN -> {
                val wasTouched = this.touchCheckAble.isTouched()
                val isTouched = checkTouch(event.x, event.y)
                if (wasTouched != isTouched) {
                    needInvalidate = true
                    if (isValueSelectionEnabled) {
                        //selectionModeOldValue.clear()
                        if (wasTouched && !this.touchCheckAble.isTouched()) {
                            this.touchCheckAble.callTouchListener()
                        }
                    }
                }
            }
            MotionEvent.ACTION_UP -> if (this.touchCheckAble.isTouched()) {
                if (checkTouch(event.x, event.y)) {
                    if (isValueSelectionEnabled) {
                        // For selection mode call listener only if selected value changed,
                        // that means that should be
                        // first(selection) click on given value.
                        /*if (selectionModeOldValue != selectedValue) {
                            selectionModeOldValue.set(selectedValue)
                            this.chart.callTouchListener()
                        }*/
                        this.touchCheckAble.callTouchListener()
                    } else {
                        this.touchCheckAble.callTouchListener()
                        this.touchCheckAble.clearTouch()
                    }
                } else {
                    this.touchCheckAble.clearTouch()
                }
                needInvalidate = true
            }
            MotionEvent.ACTION_MOVE ->                 // If value was touched and now touch point is outside of value area - clear touch and invalidate, user
                // probably moved finger away from given chart value.
                if (this.touchCheckAble.isTouched()) {
                    if (!checkTouch(event.x, event.y)) {
                        this.touchCheckAble.clearTouch()
                        needInvalidate = true
                    }
                }
            MotionEvent.ACTION_CANCEL -> if (this.touchCheckAble.isTouched()) {
                this.touchCheckAble.clearTouch()
                needInvalidate = true
            }
        }
        return needInvalidate
    }

    private fun checkTouch(touchX: Float, touchY: Float): Boolean {
        if(touchCheckAble == null) return false

        val realPoint = commutator.restRealPoint(touchX,touchY)?:return false
        return touchCheckAble.checkTouch(realPoint.x, realPoint.y)
    }

    /*private fun checkTouch(touchX: Float, touchY: Float): Boolean {
        oldSelectedValue.set(selectedValue)
        selectedValue.clear()
        if (this.chart.checkTouch(touchX, touchY)) {
            //selectedValue.set(renderer.selectedValue)
        }

        // Check if selection is still on the same value, if not return false.
        return if (oldSelectedValue.isSet && selectedValue.isSet && oldSelectedValue != selectedValue) {
            false
        } else {
            this.chart.isTouched()
        }
    }*/


    protected inner class ChartScaleGestureListener : ScaleGestureDetector.SimpleOnScaleGestureListener() {
        override fun onScale(detector: ScaleGestureDetector): Boolean {
            if (isZoomEnabled) {
                /*var scale = 2.0f - detector.scaleFactor
                if (java.lang.Float.isInfinite(scale)) {
                    scale = 1f
                }
                return chartZoomer.scale(detector.focusX, detector.focusY, scale)*/
                if (detector.isInProgress)
                    commutator.setScaleFactor(detector.focusX, detector.focusY,detector.scaleFactor)
            }
            return false
        }
    }

    protected open inner class ChartGestureListener : GestureDetector.SimpleOnGestureListener() {
        protected var scrollResult = ChartScroller.ScrollResult()
        override fun onDown(e: MotionEvent): Boolean {
            if (isScrollEnabled) {
                disallowParentInterceptTouchEvent()
                return chartScroller.startScroll()
            }
            return false
        }

        override fun onDoubleTap(e: MotionEvent): Boolean {
            return if (isZoomEnabled) {
                //chartZoomer.startZoom(e)
                commutator.setScaleFactor(e.x, e.y,1.5f)
                true
            } else false
        }

        override fun onScroll(
            e1: MotionEvent?,
            e2: MotionEvent,
            distanceX: Float,
            distanceY: Float
        ): Boolean {
            if (isScrollEnabled) {
                val canScroll = chartScroller.scroll( distanceX, distanceY, scrollResult)
                allowParentInterceptTouchEvent(scrollResult)
                return canScroll
            }
            return false
        }

        override fun onFling(
            e1: MotionEvent?,
            e2: MotionEvent,
            velocityX: Float,
            velocityY: Float
        ): Boolean {
            return if (isScrollEnabled) {
                chartScroller.fling(-velocityX.toInt(), -velocityY.toInt())
            } else false
        }


    }
}
